/*
 *  Borum, Boolean rule manipulator.
 *  Copyright (C) 2009 Jon Lennersten, Johan Hedin, Daniel Rosenberg
 *
 *  This file is part of Borum.
 *
 *  Borum is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Borum is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Borum.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mawell.borum;

import net.antonioshome.swing.treewrapper.DeepCopyNode;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.DefaultTreeModel;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Enumeration;

public class RuleModel {

    private RuleController controller;
    private RuleView view;

    private List<Term> possibleTerms = new ArrayList<Term>();    
    private List<OperandProperty> possibleProperties = new ArrayList<OperandProperty>();
    private LinkedList<TreeModel> undoModels = new LinkedList<TreeModel>();
    private int currentTreeModelIndex = -1;

    public RuleModel(RuleModel model) {
        possibleTerms = model.possibleTerms;
        possibleProperties = model.possibleProperties;
        insertNewModel(model.getSnapshot()); 
    }

    public RuleModel() {
    }

    public RuleController getController() {
        return controller;
    }

    public void setController(RuleController controller) {
        this.controller = controller;
    }

    public RuleView getView() {
        return view;
    }

    public void setView(RuleView view) {
        this.view = view;
    }

    public List<Term> getPossibleTerms() {
        return possibleTerms;
    }

    public void setPossibleTerms(List<Term> possibleTerms) {
        this.possibleTerms = possibleTerms;
    }

    public List<OperandProperty> getPossibleProperties() {
        return possibleProperties;
    }

    public void setPossibleProperties(List<OperandProperty> possibleProperties) {
        this.possibleProperties = possibleProperties;
    }

    public int getCurrentTreeModelIndex() {
        return currentTreeModelIndex;
    }

    public TreeModel undo() {
        if(currentTreeModelIndex > 0) {
            currentTreeModelIndex--;
        }
        return getCurrentTreeModel();
    }

    public TreeModel redo() {
        if(currentTreeModelIndex < undoModels.size() - 1) {
            currentTreeModelIndex++;
        }
        return getCurrentTreeModel();
    }

    public void insertNewModel(TreeModel tm) {
        if(currentTreeModelIndex < undoModels.size() - 1) {
            //Remove all redos
            undoModels.subList(currentTreeModelIndex + 1, undoModels.size()).clear();
        }
        undoModels.addLast(tm);
        currentTreeModelIndex++;
    }

    public void revertLastSnapshot() {
        undoModels.remove(currentTreeModelIndex);
        currentTreeModelIndex--;
    }

    public TreeModel getCurrentTreeModel() {
        if(currentTreeModelIndex > -1 && currentTreeModelIndex < undoModels.size()) {
          return undoModels.get(currentTreeModelIndex);
        }
        return null;
    }

    public void removeNodeFromParent(DefaultMutableTreeNode actualTreeNode) {
        if(!view.isLocked()) {
            doSnapshotOfModel();
            getCurrentTreeModel().removeNodeFromParent(actualTreeNode);
            view.updateUndoRedoLabelsAndEditedFlag();
        }
    }

    public void insertNodeInto(DeepCopyNode copy, DefaultMutableTreeNode actualTreeNode, int i) {
        if(!view.isLocked()) {
            doSnapshotOfModel();
            getCurrentTreeModel().insertNodeInto(copy, actualTreeNode, i);
            view.updateUndoRedoLabelsAndEditedFlag();
        }
    }

    public void doSnapshotOfModel() {
        if(currentTreeModelIndex < undoModels.size() - 1) {
            //Remove all redos
            undoModels.subList(currentTreeModelIndex + 1, undoModels.size()).clear();
        }
        TreeModel modelCopy = getSnapshot();

        undoModels.add(currentTreeModelIndex, modelCopy);
        currentTreeModelIndex++;
    }

    private TreeModel getSnapshot() {
        TreeNode aNode = (TreeNode) getCurrentTreeModel().getRoot();
        DefaultMutableTreeNode rootCopy;
        if(aNode instanceof DeepCopyNode) {
            rootCopy = ((DeepCopyNode) aNode).deepCopy();
        } else {
            rootCopy = new DefaultMutableTreeNode(aNode.toString());
        }
        rootCopy.setAllowsChildren(aNode.getAllowsChildren());
        TreeModel modelCopy = new TreeModel(rootCopy);
        if(aNode.getChildCount() != 0) {
            Enumeration children = aNode.children();
            while(children.hasMoreElements()) {
                TreeNode child = (TreeNode) children.nextElement();
                DefaultMutableTreeNode childCopy = recursivelyCopyNodes(modelCopy, child);
                modelCopy.insertNodeInto(childCopy, rootCopy, rootCopy.getChildCount());
                childCopy.setParent(rootCopy);
            }
        }
        return modelCopy;
    }

    private DefaultMutableTreeNode recursivelyCopyNodes(DefaultTreeModel aModel, TreeNode aNode) {
        DefaultMutableTreeNode copy;
        if(aNode instanceof DeepCopyNode) {
            copy = ((DeepCopyNode) aNode).deepCopy();
        } else {
            copy = new DefaultMutableTreeNode(aNode.toString());
        }
        copy.setAllowsChildren(aNode.getAllowsChildren());
        if(aNode.getChildCount() != 0) {
            Enumeration children = aNode.children();
            while(children.hasMoreElements()) {
                TreeNode child = (TreeNode) children.nextElement();
                DefaultMutableTreeNode childCopy = recursivelyCopyNodes(aModel, child);
                aModel.insertNodeInto(childCopy, copy, copy.getChildCount());
                childCopy.setParent(copy);
            }
        }
        return copy;
    }

    public boolean isModelFirst() {
        return currentTreeModelIndex == 0;
    }


    public boolean isModelLast() {
        return currentTreeModelIndex == undoModels.size()-1;
    }
}